# 用户认证模块

import functools
# 创建一个蓝图，用于处理用户认证相关的路由和视图函数
from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
from werkzeug.security import check_password_hash, generate_password_hash
from flaskr.db import get_db

bp = Blueprint('auth', __name__, url_prefix='/auth')
'''
创建一个蓝图对象
参数：
    - auth：蓝图的名称，用于在 URL 中引用蓝图。
    - __name__：当前模块的名称，用于确定蓝图的根路径。
    - url_prefix：指定蓝图的 URL 前缀，用于在 URL 中引用蓝图。
'''


# 注册模块
@bp.route('/register', methods=('GET', 'POST'))
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        db = get_db()
        error = None

        if not username:
            error = 'Username is required.'
        elif not password:
            error = 'Password is required.'
        if error is None:
            try:
                db.execute(
                    'INSERT INTO user (username, password) VALUES (?, ?)',
                    (username, generate_password_hash(password))
                )
                db.commit()
            except sqlite3.IntegrityError: # 发生 IntegrityError 异常执行
                error = f"User {username} is already registered."
            else: # 未发生异常执行 跳转到登陆界面
                return redirect(url_for('auth.login'))
            # finally: # 发不发生异常都执行

        flash(error)
    return render_template('auth/register.html') # 渲染注册页面

@bp.route('/login', methods=('GET', 'POST'))
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        db = get_db()
        error = None

        user = db.execute(
            'SELECT * FROM user WHERE username = ?', (username,)
        ).fetchone() # fetchone返回行查询结果，如果不存在，则返回none

        if user is None:
            error = 'Incorrect username.'
        elif not check_password_hash(user['password'], password):
            error = 'Incorrect password.'
        
        if error is None:
            session.clear()
            session['user_id'] = user['id']
            return redirect(url_for('index'))

        flash(error)

    return render_template('auth/login.html')
'''
session 
1. 创建时机：当客户端（浏览器）首次访问 Flask 应用的任何路由，且应用代码中读取或修改了 session 对象时。

2. 存储位置：Flask 默认使用 ​签名 Cookie 存储 Session 数据

    ​数据位置：所有 Session 数据会经过加密后存储在浏览器的 Cookie 中（名为 session）。

    安全性：通过 app.secret_key 签名防止篡改，但内容可被客户端看到（不存储敏感信息！）。

3.  同一浏览器共享同一个 Session

    所有标签页、iframe 共享同一 Session（因为 Cookie 相同）。
'''


@bp.route('/logout')
def logout():
    session.clear()
    return redirect(url_for('index'))   

# 为每个请求加载当前用户数据 （ 利用g对象保存 ）
'''
g 对象会在 ​Flask 处理每个 HTTP 请求时自动创建,
'''

@bp.before_app_request # 注册一个函数并让它在每一个视图函数之前运行，不管请求发到哪一个 URL。
def load_logged_in_user():
    user_id = session.get('user_id')
    
    if user_id is None:
        g.user = None
    else:
        db = get_db()
        g.user = db.execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()


# # 在执行任何视图前，检查登陆状态，利用装饰器实现，即装饰 原视图函数
def login_required(view):
    '''
    装饰器：检查用户是否登录
    '''
    @functools.wraps(view) # 保留原函数名
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login')) # 跳转到登陆界面

        return view(**kwargs) # 若已登陆，则执行原视图函数

    return wrapped_view

    # login_required 是装饰器代码

